/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.eclipse.imagen;

import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import org.eclipse.imagen.media.util.DataBufferUtils;
import org.eclipse.imagen.media.util.ImageUtil;

/**
 * An adapter class for presenting non-binary image data in a <code>ComponentSampleModel</code> format and binary image
 * data in a zero-offset byte array format even when the original data are not so stored. <code>RasterAccessor</code> is
 * meant to make the common (<code>ComponentSampleModel</code>) case fast and other formats possible without forcing the
 * <code>OpImage</code> writer to cover more than one case per non-binary data type.
 *
 * <p>When constructing a <code>RasterAccessor</code> with a source(s) that has an IndexColorModel and a destination
 * that has a non-<code>IndexColorModel</code>, <code>RasterAccessor</code> will perform expansion of the source pixels.
 * If the source(s) and the destination have an IndexColorModel, then <code>RasterAccessor</code> will assume that the
 * operation can correctly process an IndexColorModel source and will not expand the source pixels (colormap indices)
 * into color components. Refer to {@link JAI#KEY_REPLACE_INDEX_COLOR_MODEL} for a mechanism by which the destination
 * image's <code>ColorModel</code> is set to a non-<code>IndexColorModel</code> to cause <code>RasterAccessor</code> to
 * expand the source's <code>IndexColorModel</code>.
 *
 * <p>Binary data are handled as a special case. In general image data are considered to be binary when the image has a
 * single-banded <code>MultiPixelPackedSampleModel</code> with one bit per pixel. This may be verified by invoking the
 * <code>isBinary()</code> method. For this case the methods <code>getBinaryDataArray()</code> and <code>
 * copyBinaryDataToRaster()</code> should be used to access and set, respectively, the binary data in packed form. If
 * the binary data are to be accessed in expanded form, i.e., as bytes, then the usual byte methods <code>
 * getByteDataArray()</code>, <code>getByteDataArrays()</code>, and <code>copyDataToRaster()</code> should be used.
 */
public class RasterAccessor {

    /** Value indicating how far COPY_MASK info is shifted to avoid interfering with the data type info. */
    private static final int COPY_MASK_SHIFT = 7;

    /* Value indicating how many bits the COPY_MASK is */
    private static final int COPY_MASK_SIZE = 2;

    /** The bits of a FormatTag associated with how dataArrays are obtained. */
    public static final int COPY_MASK = ((1 << COPY_MASK_SIZE) - 1) << COPY_MASK_SHIFT;

    /** Flag indicating data is raster's data. */
    public static final int UNCOPIED = 0x0 << COPY_MASK_SHIFT;

    /** Flag indicating data is a copy of the raster's data. */
    public static final int COPIED = 0x1 << COPY_MASK_SHIFT;

    /** Value indicating how far EXPANSION_MASK info is shifted to avoid interfering with the data type info. */
    private static final int EXPANSION_MASK_SHIFT = COPY_MASK_SHIFT + COPY_MASK_SIZE;

    /** Value indicating how many bits the EXPANSION_MASK is */
    private static final int EXPANSION_MASK_SIZE = 2;

    /** The bits of a FormatTag associated with how ColorModels are used. */
    public static final int EXPANSION_MASK = ((1 << EXPANSION_MASK_SIZE) - 1) << EXPANSION_MASK_SHIFT;

    /** Flag indicating ColorModel data should be used only in copied case */
    public static final int DEFAULTEXPANSION = 0x0 << EXPANSION_MASK_SHIFT;

    /** Flag indicating ColorModel data should be interpreted. */
    public static final int EXPANDED = 0x1 << EXPANSION_MASK_SHIFT;

    /** Flag indicating ColorModel info should be ignored */
    public static final int UNEXPANDED = 0x02 << EXPANSION_MASK_SHIFT;

    /** The bits of a FormatTagID associated with pixel datatype. */
    public static final int DATATYPE_MASK = (0x1 << COPY_MASK_SHIFT) - 1;

    /** FormatTagID indicating data in byte arrays and uncopied. */
    public static final int TAG_BYTE_UNCOPIED = DataBuffer.TYPE_BYTE | UNCOPIED;

    /** FormatTagID indicating data in unsigned short arrays and uncopied. */
    public static final int TAG_USHORT_UNCOPIED = DataBuffer.TYPE_USHORT | UNCOPIED;

    /** FormatTagID indicating data in short arrays and uncopied. */
    public static final int TAG_SHORT_UNCOPIED = DataBuffer.TYPE_SHORT | UNCOPIED;

    /** FormatTagID indicating data in int arrays and uncopied. */
    public static final int TAG_INT_UNCOPIED = DataBuffer.TYPE_INT | UNCOPIED;

    /** FormatTagID indicating data in float arrays and uncopied. */
    public static final int TAG_FLOAT_UNCOPIED = DataBuffer.TYPE_FLOAT | UNCOPIED;

    /** FormatTagID indicating data in double arrays and uncopied. */
    public static final int TAG_DOUBLE_UNCOPIED = DataBuffer.TYPE_DOUBLE | UNCOPIED;

    /** FormatTagID indicating data in int arrays and copied. */
    public static final int TAG_INT_COPIED = DataBuffer.TYPE_INT | COPIED;

    /** FormatTagID indicating data in float arrays and copied. */
    public static final int TAG_FLOAT_COPIED = DataBuffer.TYPE_FLOAT | COPIED;

    /** FormatTagID indicating data in double arrays and copied. */
    public static final int TAG_DOUBLE_COPIED = DataBuffer.TYPE_DOUBLE | COPIED;

    /** FormatTagID indicating data in byte arrays and expanded. */
    public static final int TAG_BYTE_EXPANDED = DataBuffer.TYPE_BYTE | EXPANDED;

    /**
     * FormatTagID corresponding to the binary case. This occurs when the image has a <code>MultiPixelPackedSampleModel
     * </code> with a single band and one bit per pixel.
     */
    private static final int TAG_BINARY = DataBuffer.TYPE_BYTE | COPIED | UNEXPANDED;

    /** The raster that is the source of pixel data. */
    protected Raster raster;

    /** The width of the rectangle this RasterAccessor addresses. */
    protected int rectWidth;

    /** The height of the rectangle this RasterAccessor addresses. */
    protected int rectHeight;

    /** The x coordinate of upper-left corner of the rectangle this RasterAccessor addresses. */
    protected int rectX;

    /** The y coordinate of upper-left corner of the rectangle this RasterAccessor addresses. */
    protected int rectY;

    /** Tag indicating the data type of the data and whether it's copied */
    protected int formatTagID;

    /**
     * The image data for the binary case. The data will be packed as eight bits per byte with no bit offset, i.e., the
     * first bit in each image line will be the left-most bit of the first byte of the line. The line stride in bytes
     * will be <code>(int)((rectWidth+7)/8)</code>. The length of the array will be <code>rectHeight</code> multiplied
     * by the line stride.
     *
     * @since JAI 1.1
     */
    protected byte binaryDataArray[] = null;

    /**
     * The image data in a two-dimensional byte array. This value will be non-null only if getDataType() returns
     * DataBuffer.TYPE_BYTE. byteDataArrays.length will equal numBands. Note that often the numBands subArrays will all
     * point to the same place in memory.
     *
     * <p>For the case of binary data this variable will not be initialized until <code>getByteDataArrays()</code> or
     * <code>getByteDataArray(int b)</code> is invoked.
     */
    protected byte byteDataArrays[][] = null;

    /**
     * The image data in a two-dimensional short array. This value will be non-null only if getDataType() returns
     * DataBuffer.TYPE_USHORT or DataBuffer.TYPE_SHORT. shortDataArrays.length will equal numBands. Note that often the
     * numBands subArrays will all point to the same place in memory.
     */
    protected short shortDataArrays[][] = null;

    /**
     * The image data in a two-dimensional int array. This value will be non-null only if getDataType() returns
     * DataBuffer.TYPE_INT. intDataArrays.length will equal numBands. Note that often the numBands subArrays will all
     * point to the same place in memory.
     */
    protected int intDataArrays[][] = null;

    /**
     * The image data in a two-dimensional float array. This value will be non-null only if getDataType() returns
     * DataBuffer.TYPE_FLOAT. floatDataArrays.length will equal numBands. Note that often the numBand subArrays will all
     * point to the same place in memory.
     */
    protected float floatDataArrays[][] = null;

    /**
     * The image data in a two-dimensional double array. This value will be non-null only if getDataType() returns
     * DataBuffer.TYPE_DOUBLE. doubleDataArrays.length will equal numBands. Note that often the numBand subArrays will
     * all point to the same place in memory.
     */
    protected double doubleDataArrays[][] = null;

    /** The bandOffset + subRasterOffset + DataBufferOffset into each of the numBand data arrays */
    protected int bandDataOffsets[];

    /** Offset from a pixel's offset to a band of that pixel */
    protected int bandOffsets[];

    /** The number of bands per pixel in the data array. */
    protected int numBands;

    /** The scanline stride of the image data in each data array */
    protected int scanlineStride;

    /** The pixel stride of the image data in each data array */
    protected int pixelStride;

    /**
     * Finds the appropriate tags for the constructor, based on the SampleModel and ColorModel of all the source and
     * destination.
     *
     * @param srcs The operations sources; may be <code>null</code> which is taken to be equivalent to zero sources.
     * @param dst The operation destination.
     * @return An array containing <code>RasterFormatTag</code>s for the sources in the first src.length elements and a
     *     <code>RasterFormatTag</code> for the destination in the last element.
     * @throws NullPointerException if <code>dst</code> is <code>null</code>.
     */
    public static RasterFormatTag[] findCompatibleTags(RenderedImage srcs[], RenderedImage dst) {
        int tagIDs[];
        if (srcs != null) {
            tagIDs = new int[srcs.length + 1];
        } else {
            tagIDs = new int[1];
        }
        SampleModel dstSampleModel = dst.getSampleModel();
        int dstDataType = dstSampleModel.getTransferType();

        int defaultDataType = dstDataType;
        boolean binaryDst = ImageUtil.isBinary(dstSampleModel);
        if (binaryDst) {
            defaultDataType = DataBuffer.TYPE_BYTE;
        } else if ((dstDataType == DataBuffer.TYPE_BYTE)
                || (dstDataType == DataBuffer.TYPE_USHORT)
                || (dstDataType == DataBuffer.TYPE_SHORT)) {
            defaultDataType = DataBuffer.TYPE_INT;
        }

        // use highest precision datatype of all srcs & dst
        if (srcs != null) {
            int numSources = srcs.length;
            int i;
            for (i = 0; i < numSources; i++) {
                SampleModel srcSampleModel = srcs[i].getSampleModel();
                int srcDataType = srcSampleModel.getTransferType();
                if (!(binaryDst && ImageUtil.isBinary(srcSampleModel)) && srcDataType > defaultDataType) {
                    defaultDataType = srcDataType;
                }
            }
        }

        // Set the tag. For binary data at this point this should
        // equal DataBuffer.TYPE_BYTE | COPIED.
        int tagID = defaultDataType | COPIED;

        if (dstSampleModel instanceof ComponentSampleModel) {
            if (srcs != null) {
                int numSources = srcs.length;
                int i;
                for (i = 0; i < numSources; i++) {
                    SampleModel srcSampleModel = srcs[i].getSampleModel();
                    int srcDataType = srcSampleModel.getTransferType();
                    if (!(srcSampleModel instanceof ComponentSampleModel) || (srcDataType != dstDataType)) {
                        break;
                    }
                }
                if (i == numSources) {
                    tagID = dstDataType | UNCOPIED;
                }
            } else {
                tagID = dstDataType | UNCOPIED;
            }
        }

        // If the source has an IndexColorModel but the dest does not,
        // perform expansion of the source pixels.  If both have an
        // IndexColorModel, assume the operation knows what it is doing.
        RasterFormatTag rft[] = new RasterFormatTag[tagIDs.length];
        if (srcs != null) {
            for (int i = 0; i < srcs.length; i++) {
                // dst can't be EXPANDED
                if ((srcs[i].getColorModel() instanceof IndexColorModel)) {
                    if (dst.getColorModel() instanceof IndexColorModel) {
                        tagIDs[i] = tagID | UNEXPANDED;
                    } else {
                        tagIDs[i] = tagID | EXPANDED;
                    }
                } else if (srcs[i].getColorModel() instanceof ComponentColorModel
                        || (binaryDst && ImageUtil.isBinary(srcs[i].getSampleModel()))) {
                    tagIDs[i] = tagID | UNEXPANDED;
                } else {
                    tagIDs[i] = tagID | DEFAULTEXPANSION;
                }
            }
            tagIDs[srcs.length] = tagID | UNEXPANDED;

            for (int i = 0; i < srcs.length; i++) {
                rft[i] = new RasterFormatTag(srcs[i].getSampleModel(), tagIDs[i]);
            }
            // get the dest
            rft[srcs.length] = new RasterFormatTag(dstSampleModel, tagIDs[srcs.length]);
        } else { // no sources, dest only
            rft[0] = new RasterFormatTag(dstSampleModel, tagID | UNEXPANDED);
        }

        return rft;
    }

    /**
     * Returns the most efficient FormatTagID that is compatible with the destination SampleModel and all source
     * SampleModels. Since there is no <code>ColorModel</code> associated with a <code>SampleModel</code>, this method
     * does not expand the data buffer as it has no access to the Raster's ColorModel.
     */
    public static int findCompatibleTag(SampleModel[] srcSampleModels, SampleModel dstSampleModel) {
        int dstDataType = dstSampleModel.getTransferType();

        int tag = dstDataType | COPIED;
        if (ImageUtil.isBinary(dstSampleModel)) {
            tag = DataBuffer.TYPE_BYTE | COPIED;
        } else if (dstDataType == DataBuffer.TYPE_BYTE
                || dstDataType == DataBuffer.TYPE_USHORT
                || dstDataType == DataBuffer.TYPE_SHORT) {
            tag = TAG_INT_COPIED;
        }

        if (dstSampleModel instanceof ComponentSampleModel) {
            if (srcSampleModels != null) {
                int numSources = srcSampleModels.length;
                int i;
                for (i = 0; i < numSources; i++) {
                    int srcDataType = srcSampleModels[i].getTransferType();

                    if (!(srcSampleModels[i] instanceof ComponentSampleModel) || srcDataType != dstDataType) {
                        break;
                    }
                }
                if (i == numSources) {
                    tag = dstDataType | UNCOPIED;
                }
            } else {
                tag = dstDataType | UNCOPIED;
            }
        }
        return tag | UNEXPANDED; // only called when colormodel not around
        // so never expand
    }

    /**
     * Constructs a RasterAccessor object out of a Raster, Rectangle and formatTagID returned from
     * RasterFormat.findCompatibleTag().
     *
     * <p>The <code>RasterFormatTag</code> must agree with the raster's <code>SampleModel</code> and <code>ColorModel
     * </code>. It is best to obtain the correct tag using the <code>findCompatibleTags</code> static method.
     *
     * @param raster The raster to be accessed
     * @param rect A <code>Rectangle</code> from the raster to be accessed
     * @param rft The <code>RasterFormatTag</code> associated with the Raster
     * @param theColorModel The <code>ColorModel</code> for color components
     * @throws ClassCastException if the data type of <code>RasterFormatTag</code> does not agree with the actual data
     *     type of the <code>Raster</code>.
     * @throws IllegalArgumentException if <code>raster</code>, <code>rect</code>, or <code>rft</code> is <code>null
     *     </code>.
     * @throws IllegalArgumentException if the <code>Rectangle</code> is not contained within <code>Raster</code>'s
     *     bounds.
     */
    public RasterAccessor(Raster raster, Rectangle rect, RasterFormatTag rft, ColorModel theColorModel) {

        if (raster == null || rect == null || rft == null) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        // If requesting a region that lies outside the bounds,
        // throw an exception.
        if (!raster.getBounds().contains(rect)) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterAccessor2"));
        }

        this.raster = raster;
        this.rectX = rect.x;
        this.rectY = rect.y;
        this.rectWidth = rect.width;
        this.rectHeight = rect.height;
        this.formatTagID = rft.getFormatTagID();
        if ((formatTagID & COPY_MASK) == UNCOPIED) {

            this.numBands = rft.getNumBands();
            this.pixelStride = rft.getPixelStride();

            ComponentSampleModel csm = (ComponentSampleModel) raster.getSampleModel();
            this.scanlineStride = csm.getScanlineStride();

            int bankIndices[] = null;

            // if the rft isPixelSequential we can rely on it's
            // version of bandOffsets and bankIndicies.  If it's
            // not the SampleModel passed in might not completely
            // match the one that was passed to the the
            // RasterFormatTag constructor so we have to get them
            // from the passed in Raster/SampleModel
            if (rft.isPixelSequential()) {
                this.bandOffsets = rft.getBandOffsets();
                bankIndices = rft.getBankIndices();
            } else {
                this.bandOffsets = csm.getBandOffsets();
                bankIndices = csm.getBankIndices();
            }

            this.bandDataOffsets = new int[numBands];

            int dataBufferOffsets[] = raster.getDataBuffer().getOffsets();

            int subRasterOffset = (rectY - raster.getSampleModelTranslateY()) * scanlineStride
                    + (rectX - raster.getSampleModelTranslateX()) * pixelStride;

            if (dataBufferOffsets.length == 1) {
                int theDataBufferOffset = dataBufferOffsets[0];
                for (int i = 0; i < numBands; i++) {
                    bandDataOffsets[i] = bandOffsets[i] + theDataBufferOffset + subRasterOffset;
                }
            } else if (dataBufferOffsets.length == bandDataOffsets.length) {
                for (int i = 0; i < numBands; i++) {
                    bandDataOffsets[i] = bandOffsets[i] + dataBufferOffsets[i] + subRasterOffset;
                }
            } else {
                throw new RuntimeException(JaiI18N.getString("RasterAccessor0"));
            }

            switch (formatTagID & DATATYPE_MASK) {
                case DataBuffer.TYPE_BYTE:
                    DataBufferByte dbb = (DataBufferByte) raster.getDataBuffer();
                    byteDataArrays = new byte[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        byteDataArrays[i] = dbb.getData(bankIndices[i]);
                    }
                    break;

                case DataBuffer.TYPE_USHORT:
                    DataBufferUShort dbus = (DataBufferUShort) raster.getDataBuffer();
                    shortDataArrays = new short[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        shortDataArrays[i] = dbus.getData(bankIndices[i]);
                    }
                    break;

                case DataBuffer.TYPE_SHORT:
                    DataBufferShort dbs = (DataBufferShort) raster.getDataBuffer();
                    shortDataArrays = new short[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        shortDataArrays[i] = dbs.getData(bankIndices[i]);
                    }
                    break;

                case DataBuffer.TYPE_INT:
                    DataBufferInt dbi = (DataBufferInt) raster.getDataBuffer();
                    intDataArrays = new int[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        intDataArrays[i] = dbi.getData(bankIndices[i]);
                    }
                    break;

                case DataBuffer.TYPE_FLOAT:
                    DataBuffer dbf = (DataBuffer) raster.getDataBuffer();
                    floatDataArrays = new float[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        floatDataArrays[i] = DataBufferUtils.getDataFloat(dbf, bankIndices[i]);
                    }
                    break;

                case DataBuffer.TYPE_DOUBLE:
                    DataBuffer dbd = (DataBuffer) raster.getDataBuffer();
                    doubleDataArrays = new double[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        doubleDataArrays[i] = DataBufferUtils.getDataDouble(dbd, bankIndices[i]);
                    }
                    break;
            }
            // only do this if not copied and expanded
            if ((formatTagID & EXPANSION_MASK) == EXPANDED && theColorModel instanceof IndexColorModel) {
                IndexColorModel icm = (IndexColorModel) theColorModel;

                int newNumBands = icm.getNumComponents();

                int mapSize = icm.getMapSize();
                int newBandDataOffsets[] = new int[newNumBands];
                int newScanlineStride = rectWidth * newNumBands;
                int newPixelStride = newNumBands;
                byte ctable[][] = new byte[newNumBands][mapSize];

                icm.getReds(ctable[0]);
                icm.getGreens(ctable[1]);
                icm.getBlues(ctable[2]);
                byte rtable[] = ctable[0];
                byte gtable[] = ctable[1];
                byte btable[] = ctable[2];

                byte atable[] = null;
                if (newNumBands == 4) {
                    icm.getAlphas(ctable[3]);
                    atable = ctable[3];
                }

                for (int i = 0; i < newNumBands; i++) {
                    newBandDataOffsets[i] = i;
                }

                switch (formatTagID & DATATYPE_MASK) {
                    case DataBuffer.TYPE_BYTE:
                        {
                            byte newBArray[] = new byte[rectWidth * rectHeight * newNumBands];
                            byte byteDataArray[] = byteDataArrays[0];
                            int scanlineOffset = bandDataOffsets[0];
                            int newScanlineOffset = 0;
                            for (int j = 0; j < rectHeight; j++) {
                                int pixelOffset = scanlineOffset;
                                int newPixelOffset = newScanlineOffset;
                                for (int i = 0; i < rectWidth; i++) {
                                    int index = byteDataArray[pixelOffset] & 0xff;
                                    for (int k = 0; k < newNumBands; k++) {
                                        newBArray[newPixelOffset + k] = ctable[k][index];
                                    }
                                    pixelOffset += pixelStride;
                                    newPixelOffset += newPixelStride;
                                }
                                scanlineOffset += scanlineStride;
                                newScanlineOffset += newScanlineStride;
                            }
                            byteDataArrays = new byte[newNumBands][];
                            for (int i = 0; i < newNumBands; i++) {
                                byteDataArrays[i] = newBArray;
                            }
                        }
                        break;

                    case DataBuffer.TYPE_USHORT:
                        {
                            short newIArray[] = new short[rectWidth * rectHeight * newNumBands];
                            short shortDataArray[] = shortDataArrays[0];
                            int scanlineOffset = bandDataOffsets[0];
                            int newScanlineOffset = 0;
                            for (int j = 0; j < rectHeight; j++) {
                                int pixelOffset = scanlineOffset;
                                int newPixelOffset = newScanlineOffset;
                                for (int i = 0; i < rectWidth; i++) {
                                    int index = (shortDataArray[pixelOffset] & 0xffff);
                                    for (int k = 0; k < newNumBands; k++) {
                                        newIArray[newPixelOffset + k] = (short) (ctable[k][index] & 0xff);
                                    }
                                    pixelOffset += pixelStride;
                                    newPixelOffset += newPixelStride;
                                }
                                scanlineOffset += scanlineStride;
                                newScanlineOffset += newScanlineStride;
                            }

                            shortDataArrays = new short[newNumBands][];
                            for (int i = 0; i < newNumBands; i++) {
                                shortDataArrays[i] = newIArray;
                            }
                        }
                        break;

                    case DataBuffer.TYPE_SHORT:
                        {
                            short newIArray[] = new short[rectWidth * rectHeight * newNumBands];
                            short shortDataArray[] = shortDataArrays[0];
                            int scanlineOffset = bandDataOffsets[0];
                            int newScanlineOffset = 0;
                            for (int j = 0; j < rectHeight; j++) {
                                int pixelOffset = scanlineOffset;
                                int newPixelOffset = newScanlineOffset;
                                for (int i = 0; i < rectWidth; i++) {
                                    int index = shortDataArray[pixelOffset];
                                    for (int k = 0; k < newNumBands; k++) {
                                        newIArray[newPixelOffset + k] = (short) (ctable[k][index] & 0xff);
                                    }
                                    pixelOffset += pixelStride;
                                    newPixelOffset += newPixelStride;
                                }
                                scanlineOffset += scanlineStride;
                                newScanlineOffset += newScanlineStride;
                            }

                            shortDataArrays = new short[newNumBands][];
                            for (int i = 0; i < newNumBands; i++) {
                                shortDataArrays[i] = newIArray;
                            }
                        }
                        break;

                    case DataBuffer.TYPE_INT:
                        {
                            int newIArray[] = new int[rectWidth * rectHeight * newNumBands];
                            int intDataArray[] = intDataArrays[0];
                            int scanlineOffset = bandDataOffsets[0];
                            int newScanlineOffset = 0;
                            for (int j = 0; j < rectHeight; j++) {
                                int pixelOffset = scanlineOffset;
                                int newPixelOffset = newScanlineOffset;
                                for (int i = 0; i < rectWidth; i++) {
                                    int index = intDataArray[pixelOffset];
                                    for (int k = 0; k < newNumBands; k++) {
                                        newIArray[newPixelOffset + k] = (ctable[k][index] & 0xff);
                                    }
                                    pixelOffset += pixelStride;
                                    newPixelOffset += newPixelStride;
                                }
                                scanlineOffset += scanlineStride;
                                newScanlineOffset += newScanlineStride;
                            }

                            intDataArrays = new int[newNumBands][];
                            for (int i = 0; i < newNumBands; i++) {
                                intDataArrays[i] = newIArray;
                            }
                        }
                        break;

                    case DataBuffer.TYPE_FLOAT:
                        {
                            float newFArray[] = new float[rectWidth * rectHeight * newNumBands];
                            float floatDataArray[] = floatDataArrays[0];
                            int scanlineOffset = bandDataOffsets[0];
                            int newScanlineOffset = 0;
                            for (int j = 0; j < rectHeight; j++) {
                                int pixelOffset = scanlineOffset;
                                int newPixelOffset = newScanlineOffset;
                                for (int i = 0; i < rectWidth; i++) {
                                    int index = (int) floatDataArray[pixelOffset];
                                    for (int k = 0; k < newNumBands; k++) {
                                        newFArray[newPixelOffset + k] = (ctable[k][index] & 0xff);
                                    }
                                    pixelOffset += pixelStride;
                                    newPixelOffset += newPixelStride;
                                }
                                scanlineOffset += scanlineStride;
                                newScanlineOffset += newScanlineStride;
                            }
                            floatDataArrays = new float[newNumBands][];
                            for (int i = 0; i < newNumBands; i++) {
                                floatDataArrays[i] = newFArray;
                            }
                        }
                        break;

                    case DataBuffer.TYPE_DOUBLE:
                        {
                            double newDArray[] = new double[rectWidth * rectHeight * newNumBands];
                            double doubleDataArray[] = doubleDataArrays[0];
                            int scanlineOffset = bandDataOffsets[0];
                            int newScanlineOffset = 0;
                            for (int j = 0; j < rectHeight; j++) {
                                int pixelOffset = scanlineOffset;
                                int newPixelOffset = newScanlineOffset;
                                for (int i = 0; i < rectWidth; i++) {
                                    int index = (int) doubleDataArray[pixelOffset];
                                    for (int k = 0; k < newNumBands; k++) {
                                        newDArray[newPixelOffset + k] = (ctable[k][index] & 0xff);
                                    }
                                    pixelOffset += pixelStride;
                                    newPixelOffset += newPixelStride;
                                }
                                scanlineOffset += scanlineStride;
                                newScanlineOffset += newScanlineStride;
                            }
                            doubleDataArrays = new double[newNumBands][];
                            for (int i = 0; i < newNumBands; i++) {
                                doubleDataArrays[i] = newDArray;
                            }
                        }
                        break;
                }
                this.numBands = newNumBands;
                this.pixelStride = newPixelStride;
                this.scanlineStride = newScanlineStride;
                this.bandDataOffsets = newBandDataOffsets;
                this.bandOffsets = newBandDataOffsets;
            }
        } else if ((formatTagID & COPY_MASK) == COPIED
                && (formatTagID & EXPANSION_MASK) != UNEXPANDED
                && theColorModel != null) {
            this.numBands = theColorModel instanceof IndexColorModel
                    ? theColorModel.getNumComponents()
                    : raster.getSampleModel().getNumBands();
            this.pixelStride = this.numBands;
            this.scanlineStride = rectWidth * numBands;
            this.bandOffsets = new int[numBands];

            for (int i = 0; i < numBands; i++) {
                bandOffsets[i] = i;
            }
            this.bandDataOffsets = bandOffsets;

            Object odata = null;
            int offset = 0;

            int[] components = new int[theColorModel.getNumComponents()];

            switch (formatTagID & DATATYPE_MASK) {
                case DataBuffer.TYPE_INT:
                    int idata[] = new int[rectWidth * rectHeight * numBands];
                    intDataArrays = new int[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        intDataArrays[i] = idata;
                    }

                    odata = raster.getDataElements(rectX, rectY, null);
                    offset = 0;

                    // Workaround for bug in BytePackedRaster
                    byte[] bdata = null;

                    if (odata instanceof byte[]) {
                        bdata = (byte[]) odata;
                    }

                    for (int j = rectY; j < rectY + rectHeight; j++) {
                        for (int i = rectX; i < rectX + rectWidth; i++) {
                            if (bdata != null) {
                                bdata[0] = (byte) raster.getSample(i, j, 0);
                            } else {
                                raster.getDataElements(i, j, odata);
                            }

                            theColorModel.getComponents(odata, components, 0);

                            idata[offset] = components[0];
                            idata[offset + 1] = components[1];
                            idata[offset + 2] = components[2];
                            if (numBands > 3) {
                                idata[offset + 3] = components[3];
                            }

                            offset += pixelStride;
                        }
                    }
                    break;

                case DataBuffer.TYPE_FLOAT:
                    float fdata[] = new float[rectWidth * rectHeight * numBands];
                    floatDataArrays = new float[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        floatDataArrays[i] = fdata;
                    }
                    odata = null;
                    offset = 0;
                    for (int j = rectY; j < rectY + rectHeight; j++) {
                        for (int i = rectX; i < rectX + rectWidth; i++) {
                            odata = raster.getDataElements(i, j, odata);

                            theColorModel.getComponents(odata, components, 0);

                            fdata[offset] = components[0];
                            fdata[offset + 1] = components[1];
                            fdata[offset + 2] = components[2];
                            if (numBands > 3) {
                                fdata[offset + 3] = components[3];
                            }
                            offset += pixelStride;
                        }
                    }
                    break;

                case DataBuffer.TYPE_DOUBLE:
                    double ddata[] = new double[rectWidth * rectHeight * numBands];
                    doubleDataArrays = new double[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        doubleDataArrays[i] = ddata;
                    }
                    odata = null;
                    offset = 0;
                    for (int j = rectY; j < rectY + rectHeight; j++) {
                        for (int i = rectX; i < rectX + rectWidth; i++) {
                            odata = raster.getDataElements(i, j, odata);

                            theColorModel.getComponents(odata, components, 0);

                            ddata[offset] = components[0];
                            ddata[offset + 1] = components[1];
                            ddata[offset + 2] = components[2];
                            if (numBands > 3) {
                                ddata[offset + 3] = components[3];
                            }
                            offset += pixelStride;
                        }
                    }
                    break;
            }
        } else {
            // if ((formatTagID & COPY_MASK) == COPIED &&
            // (formatTagID & EXPANSION_MASK) == UNEXPANDED) {
            // this has become a catchall case.  Specifically for
            // Rasters with null colormodels.  So we take out the
            // if as the boolean clause will get way complicated
            // otherwise.
            this.numBands = rft.getNumBands();
            this.pixelStride = this.numBands;
            this.scanlineStride = rectWidth * numBands;
            this.bandDataOffsets = rft.getBandOffsets();
            this.bandOffsets = this.bandDataOffsets;

            switch (formatTagID & DATATYPE_MASK) {
                case DataBuffer.TYPE_INT:
                    int idata[] = raster.getPixels(rectX, rectY, rectWidth, rectHeight, (int[]) null);
                    intDataArrays = new int[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        intDataArrays[i] = idata;
                    }
                    break;

                case DataBuffer.TYPE_FLOAT:
                    float fdata[] = raster.getPixels(rectX, rectY, rectWidth, rectHeight, (float[]) null);
                    floatDataArrays = new float[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        floatDataArrays[i] = fdata;
                    }
                    break;

                case DataBuffer.TYPE_DOUBLE:
                    double ddata[] = raster.getPixels(rectX, rectY, rectWidth, rectHeight, (double[]) null);
                    doubleDataArrays = new double[numBands][];
                    for (int i = 0; i < numBands; i++) {
                        doubleDataArrays[i] = ddata;
                    }
                    break;
            }
        }
    }

    /** Returns the x coordinate of the upper-left corner of the RasterAccessor's accessible area. */
    public int getX() {
        return rectX;
    }

    /** Returns the y coordinate of the upper-left corner of the RasterAccessor's accessible area. */
    public int getY() {
        return rectY;
    }

    /** Returns the width of the RasterAccessor's accessible area. */
    public int getWidth() {
        return rectWidth;
    }

    /** Returns the height of the RasterAccessor's accessible area. */
    public int getHeight() {
        return rectHeight;
    }

    /** Returns the numBands of the presented area. */
    public int getNumBands() {
        return numBands;
    }

    /**
     * Whether the <code>RasterAccessor</code> represents binary data. This occurs when the <code>Raster</code> has a
     * <code>MultiPixelPackedSampleModel</code> with a single band and one bit per pixel.
     *
     * @since JAI 1.1
     */
    public boolean isBinary() {
        return (formatTagID & TAG_BINARY) == TAG_BINARY && ImageUtil.isBinary(raster.getSampleModel());
    }

    /**
     * For the case of binary data (<code>isBinary()</code> returns <code>true</code>), return the binary data as a
     * packed byte array. The data will be packed as eight bits per byte with no bit offset, i.e., the first bit in each
     * image line will be the left-most of the first byte of the line. The line stride in bytes will be <code>
     * (int)((getWidth()+7)/8)</code>. The length of the returned array will be the line stride multiplied by <code>
     * getHeight()</code>
     *
     * @return the binary data as a packed array of bytes with zero offset of <code>null</code> if the data are not
     *     binary.
     * @since JAI 1.1
     */
    public byte[] getBinaryDataArray() {
        if (binaryDataArray == null && isBinary()) {
            binaryDataArray = ImageUtil.getPackedBinaryData(raster, new Rectangle(rectX, rectY, rectWidth, rectHeight));
        }
        return binaryDataArray;
    }

    /**
     * Returns the image data as a byte array. Non-null only if getDataType = DataBuffer.TYPE_BYTE.
     *
     * <p>For the case of binary data the corresponding instance variable <code>byteDataArrays</code> will not be
     * initialized until this method or <code>getByteDataArray(int b)</code> is invoked. The binary data will be
     * returned as bytes with value 0 or 1.
     */
    public byte[][] getByteDataArrays() {
        if (byteDataArrays == null && isBinary()) {
            byte[] bdata = ImageUtil.getUnpackedBinaryData(raster, new Rectangle(rectX, rectY, rectWidth, rectHeight));
            byteDataArrays = new byte[][] {bdata};
        }
        return byteDataArrays;
    }

    /**
     * Returns the image data as a byte array for a specific band. Non-null only if getDataType = DataBuffer.TYPE_BYTE.
     */
    public byte[] getByteDataArray(int b) {
        byte[][] bda = getByteDataArrays();
        return (bda == null ? null : bda[b]);
    }

    /**
     * Returns the image data as a short array. Non-null only if getDataType = DataBuffer.TYPE_USHORT or
     * DataBuffer.TYPE_SHORT.
     */
    public short[][] getShortDataArrays() {
        return shortDataArrays;
    }

    /**
     * Returns the image data as a short array for a specific band. Non-null only if getDataType =
     * DataBuffer.TYPE_USHORT or DataBuffer.TYPE_SHORT.
     */
    public short[] getShortDataArray(int b) {
        return (shortDataArrays == null ? null : shortDataArrays[b]);
    }

    /** Returns the image data as an int array. Non-null only if getDataType = DataBuffer.TYPE_INT. */
    public int[][] getIntDataArrays() {
        return intDataArrays;
    }

    /**
     * Returns the image data as an int array for a specific band. Non-null only if getDataType = DataBuffer.TYPE_INT.
     */
    public int[] getIntDataArray(int b) {
        return (intDataArrays == null ? null : intDataArrays[b]);
    }

    /** Returns the image data as a float array. Non-null only if getDataType = DataBuffer.TYPE_FLOAT. */
    public float[][] getFloatDataArrays() {
        return floatDataArrays;
    }

    /**
     * Returns the image data as a float array for a specific band. Non-null only if getDataType =
     * DataBuffer.TYPE_FLOAT.
     */
    public float[] getFloatDataArray(int b) {
        return (floatDataArrays == null ? null : floatDataArrays[b]);
    }

    /** Returns the image data as a double array. Non-null only if getDataType = DataBuffer.TYPE_DOUBLE */
    public double[][] getDoubleDataArrays() {
        return doubleDataArrays;
    }

    /**
     * Returns the image data as a double array for a specific band. Non-null only if getDataType =
     * DataBuffer.TYPE_DOUBLE
     */
    public double[] getDoubleDataArray(int b) {
        return (doubleDataArrays == null ? null : doubleDataArrays[b]);
    }

    /**
     * Returns the image data as an Object for a specific band.
     *
     * @param b The index of the image band of interest.
     */
    public Object getDataArray(int b) {
        Object dataArray = null;
        switch (getDataType()) {
            case DataBuffer.TYPE_BYTE:
                dataArray = getByteDataArray(b);
                break;

            case DataBuffer.TYPE_SHORT:
            case DataBuffer.TYPE_USHORT:
                dataArray = getShortDataArray(b);
                break;

            case DataBuffer.TYPE_INT:
                dataArray = getIntDataArray(b);
                break;

            case DataBuffer.TYPE_FLOAT:
                dataArray = getFloatDataArray(b);
                break;

            case DataBuffer.TYPE_DOUBLE:
                dataArray = getDoubleDataArray(b);
                break;

            default:
                dataArray = null;
        }

        return dataArray;
    }

    /** Returns the bandDataOffsets into the dataArrays. */
    public int[] getBandOffsets() {
        return bandDataOffsets;
    }

    /** Returns the offset of all band's samples from any pixel offset. */
    public int[] getOffsetsForBands() {
        return bandOffsets;
    }

    /** Returns the offset of a specific band's first sample into the DataBuffer including the DataBuffer's offset. */
    public int getBandOffset(int b) {
        return bandDataOffsets[b];
    }

    /** Returns the offset of a specified band's sample from any pixel offset. */
    public int getOffsetForBand(int b) {
        return bandOffsets[b];
    }

    /**
     * Returns the scanlineStride for the image data.
     *
     * <p>For binary data this stride is applies to the arrays returned by <code>getByteDataArray()</code> and <code>
     * getByteDataArrays()</code> if the data are accessed as bytes; it does not apply to the array returned by <code>
     * getBinaryDataArray()</code> when the data are accessed as bits packed into bytes.
     */
    public int getScanlineStride() {
        return scanlineStride;
    }

    /** Returns the pixelStride for the image data. */
    public int getPixelStride() {
        return pixelStride;
    }

    /**
     * Returns the data type of the RasterAccessor object. Note that this datatype is not necessarily the same data type
     * as the underlying raster.
     */
    public int getDataType() {
        return formatTagID & DATATYPE_MASK;
    }

    /** Returns true if the RasterAccessors's data is copied from it's raster. */
    public boolean isDataCopy() {
        return ((formatTagID & COPY_MASK) == COPIED);
    }

    /**
     * For the case of binary data (<code>isBinary()</code> returns <code>true</code>), copy the binary data back into
     * the <code>Raster</code> of the <code>RasterAccessor</code>. If this method is invoked in the non-binary case it
     * does nothing. Any bit offset in the original <code>SampleModel</code> will be accounted for.
     *
     * @since JAI 1.1
     */
    // Note: ALL branches of this method have been tested. (bpb 10 May 2000)
    public void copyBinaryDataToRaster() {
        if (binaryDataArray == null || !isBinary()) {
            return;
        }

        ImageUtil.setPackedBinaryData(
                binaryDataArray,
                (WritableRaster) raster,
                new Rectangle(
                        rectX, rectY,
                        rectWidth, rectHeight));
    }

    /**
     * Copies data back into the RasterAccessor's raster. Note that the data is cast from the intermediate data format
     * to the raster's format. If clamping is needed, the call clampDataArrays() method needs to be called before
     * calling the copyDataToRaster() method. Note: the raster is expected to be writable - typically a destination
     * raster - otherwise, a run-time exception will occur.
     *
     * <p>If the data are binary, then the target bit will be set if and only if the corresponding byte is non-zero.
     */
    public void copyDataToRaster() {
        if (isDataCopy()) {

            // Writeback should only be necessary on destRasters which
            // should be writable so this cast should succeed.
            WritableRaster wr = (WritableRaster) raster;
            switch (getDataType()) {
                case DataBuffer.TYPE_BYTE:
                    // Note: ALL branches of this case have been tested.
                    // (bpb 10 May 2000)
                    if (!isBinary()) {
                        // If this exception occurs then there is a logic
                        // error within this accessor since the only case
                        // wherein byte data should be COPIED is when the
                        // data set is binary.
                        throw new RuntimeException(JaiI18N.getString("RasterAccessor1"));
                    }

                    // This case only occurs for binary src and dst.

                    ImageUtil.setUnpackedBinaryData(
                            byteDataArrays[0], wr, new Rectangle(rectX, rectY, rectWidth, rectHeight));
                    break;
                case DataBuffer.TYPE_INT:
                    wr.setPixels(rectX, rectY, rectWidth, rectHeight, intDataArrays[0]);
                    break;

                case DataBuffer.TYPE_FLOAT:
                    wr.setPixels(rectX, rectY, rectWidth, rectHeight, floatDataArrays[0]);
                    break;

                case DataBuffer.TYPE_DOUBLE:
                    wr.setPixels(rectX, rectY, rectWidth, rectHeight, doubleDataArrays[0]);
                    break;
            }
        }
    }

    /**
     * Indicates if the RasterAccessor has a larger dynamic range than the underlying Raster. Except in special cases,
     * where the op knows something special, this call will determine whether or not clampDataArrays() needs to be
     * called.
     */
    public boolean needsClamping() {
        int bits[] = raster.getSampleModel().getSampleSize();

        // Do we even need a clamp?  We do if there's any band
        // of the source image stored in that's less than 32 bits
        // and is stored in a byte, short or int format.  (The automatic
        // casts between floats/doubles and 32-bit ints in setPixel()
        // do what we want.)

        for (int i = 0; i < bits.length; i++) {
            if (bits[i] < 32) {
                return true;
            }
        }
        return false;
    }

    /**
     * Clamps data array values to a range that the underlying raster can deal with. For example, if the underlying
     * raster stores data as bytes, but the samples are unpacked into integer arrays by the RasterAccessor for an
     * operation, the operation will need to call clampDataArrays() so that the data in the int arrays is restricted to
     * the range 0..255 before a setPixels() call is made on the underlying raster. Note that some operations (for
     * example, lookup) can guarantee that their results don't need clamping so they can call
     * RasterAccessor.copyDataToRaster() without first calling this function.
     */
    public void clampDataArrays() {
        int bits[] = raster.getSampleModel().getSampleSize();

        // Do we even need a clamp?  We do if there's any band
        // of the source image stored in that's less than 32 bits
        // and is stored in a byte, short or int format.  (The automatic
        // casts between floats/doubles and 32-bit ints in setPixel()
        // do what we want.)

        boolean needClamp = false;
        boolean uniformBitSize = true;
        int bitSize = bits[0];
        for (int i = 0; i < bits.length; i++) {
            if (bits[i] < 32) {
                needClamp = true;
            }
            if (bits[i] != bitSize) {
                uniformBitSize = false;
            }
        }
        if (!needClamp) {
            return;
        }

        int dataType = raster.getDataBuffer().getDataType();
        double hiVals[] = new double[bits.length];
        double loVals[] = new double[bits.length];

        if (dataType == DataBuffer.TYPE_USHORT && uniformBitSize && bits[0] == 16) {
            for (int i = 0; i < bits.length; i++) {
                hiVals[i] = (double) 0xFFFF;
                loVals[i] = (double) 0;
            }
        } else if (dataType == DataBuffer.TYPE_SHORT && uniformBitSize && bits[0] == 16) {
            for (int i = 0; i < bits.length; i++) {
                hiVals[i] = (double) Short.MAX_VALUE;
                loVals[i] = (double) Short.MIN_VALUE;
            }
        } else if (dataType == DataBuffer.TYPE_INT && uniformBitSize && bits[0] == 32) {
            for (int i = 0; i < bits.length; i++) {
                hiVals[i] = (double) Integer.MAX_VALUE;
                loVals[i] = (double) Integer.MIN_VALUE;
            }
        } else {
            for (int i = 0; i < bits.length; i++) {
                hiVals[i] = (double) ((1 << bits[i]) - 1);
                loVals[i] = (double) 0;
            }
        }
        clampDataArray(hiVals, loVals);
    }

    private void clampDataArray(double hiVals[], double loVals[]) {
        switch (getDataType()) {
            case DataBuffer.TYPE_INT:
                clampIntArrays(toIntArray(hiVals), toIntArray(loVals));
                break;

            case DataBuffer.TYPE_FLOAT:
                clampFloatArrays(toFloatArray(hiVals), toFloatArray(loVals));
                break;

            case DataBuffer.TYPE_DOUBLE:
                clampDoubleArrays(hiVals, loVals);
                break;
        }
    }

    private int[] toIntArray(double vals[]) {
        int returnVals[] = new int[vals.length];
        for (int i = 0; i < vals.length; i++) {
            returnVals[i] = (int) vals[i];
        }
        return returnVals;
    }

    private float[] toFloatArray(double vals[]) {
        float returnVals[] = new float[vals.length];
        for (int i = 0; i < vals.length; i++) {
            returnVals[i] = (float) vals[i];
        }
        return returnVals;
    }

    private void clampIntArrays(int hiVals[], int loVals[]) {
        int width = rectWidth;
        int height = rectHeight;
        for (int k = 0; k < numBands; k++) {
            int data[] = intDataArrays[k];
            int scanlineOffset = bandDataOffsets[k];
            int hiVal = hiVals[k];
            int loVal = loVals[k];
            for (int j = 0; j < height; j++) {
                int pixelOffset = scanlineOffset;
                for (int i = 0; i < width; i++) {
                    int tmp = data[pixelOffset];
                    if (tmp < loVal) {
                        data[pixelOffset] = loVal;
                    } else if (tmp > hiVal) {
                        data[pixelOffset] = hiVal;
                    }
                    pixelOffset += pixelStride;
                }
                scanlineOffset += scanlineStride;
            }
        }
    }

    private void clampFloatArrays(float hiVals[], float loVals[]) {
        int width = rectWidth;
        int height = rectHeight;
        for (int k = 0; k < numBands; k++) {
            float data[] = floatDataArrays[k];
            int scanlineOffset = bandDataOffsets[k];
            float hiVal = hiVals[k];
            float loVal = loVals[k];
            for (int j = 0; j < height; j++) {
                int pixelOffset = scanlineOffset;
                for (int i = 0; i < width; i++) {
                    float tmp = data[pixelOffset];
                    if (tmp < loVal) {
                        data[pixelOffset] = loVal;
                    } else if (tmp > hiVal) {
                        data[pixelOffset] = hiVal;
                    }
                    pixelOffset += pixelStride;
                }
                scanlineOffset += scanlineStride;
            }
        }
    }

    private void clampDoubleArrays(double hiVals[], double loVals[]) {
        int width = rectWidth;
        int height = rectHeight;
        for (int k = 0; k < numBands; k++) {
            double data[] = doubleDataArrays[k];
            int scanlineOffset = bandDataOffsets[k];
            double hiVal = hiVals[k];
            double loVal = loVals[k];
            for (int j = 0; j < height; j++) {
                int pixelOffset = scanlineOffset;
                for (int i = 0; i < width; i++) {
                    double tmp = data[pixelOffset];
                    if (tmp < loVal) {
                        data[pixelOffset] = loVal;
                    } else if (tmp > hiVal) {
                        data[pixelOffset] = hiVal;
                    }
                    pixelOffset += pixelStride;
                }
                scanlineOffset += scanlineStride;
            }
        }
    }
}
